#ifndef COCKPITVBAR_H
#define COCKPITVBAR_H

#include "CockpitBar.h"

#include <QtCore/QString>
#include <QtGui/QBrush>
#include <QtGui/QPainter>
#include <QtGui/QPen>

namespace CockpitVBarUtils
{
    enum class ValueRisingDirection
    {
        Up,
        Down
    };

    enum class TextPosition
    {
        Left,
        Right
    };

    struct UnitsBetweenLines
    {
        std::function<int(int)> short_line, bold_short_line, bold_long_line;
    };
}

template <typename Float>
class CockpitVBar : public CockpitBar<Float>
{
public:
    CockpitVBar(Float value
                , typename CockpitBar<Float>::RestrictionPolicy restriction_policy = CockpitBar<Float>::NO_RESTRICTION
                , CockpitVBarUtils::ValueRisingDirection value_rising_direction = CockpitVBarUtils::ValueRisingDirection::Up
                , CockpitVBarUtils::TextPosition text_position = CockpitVBarUtils::TextPosition::Left
                , CockpitVBarUtils::UnitsBetweenLines units_between_lines = { [](int) -> int { return 1; }
                                                                            , [](int) -> int { return 5; }
                                                                            , [](int) -> int { return 10; }}
                )
    : CockpitBar<Float>(value, restriction_policy)
    , units_between_lines(units_between_lines)
    , _value_rising_direction(value_rising_direction)
    , _text_position(text_position)
    {}

protected:
    CockpitVBarUtils::UnitsBetweenLines units_between_lines;

    void paintEvent(QPaintEvent * event) override
    {
        CockpitBar<Float>::paintEvent(event);

        QPen black_pen(Qt::black);
        black_pen.setWidth(2);

        QPainter painter(this);

        const int ceiled_value = std::ceil(this->value());
        int ceiled_ceiled_value = ceiled_value % this->units_between_lines.short_line(ceiled_value);
        if (ceiled_ceiled_value < 0)
        {
            ceiled_ceiled_value = -ceiled_ceiled_value;
        } else if (ceiled_ceiled_value > 0)
        {
            ceiled_ceiled_value = this->units_between_lines.short_line(ceiled_value) - ceiled_ceiled_value;
        }
        const Float value_shift_right = std::abs(this->value() - ceiled_value) + ceiled_ceiled_value;
        const int pixels_shift_right = std::round(value_shift_right / this->units_between_lines.short_line(ceiled_value) * _PIXELS_BETWEEN_LINES);
        
        const Float top_side_y = this->height() * _SIDE_PADDING_MULTIPLIER;
        const Float bottom_side_y = this->height() - top_side_y;

        const Float central_line_y = this->height() / 2.0 + (_value_rising_direction == CockpitVBarUtils::ValueRisingDirection::Up ? -1.0 : 1.0) * pixels_shift_right;

        {
            const Float centre_x = this->width() / 2.0;
            const Float centre_y = this->height() / 2.0;

            const Float line_side_x = (_text_position == CockpitVBarUtils::TextPosition::Left ? this->width() : 0);
            
            black_pen.setWidth(2);
            painter.setPen(black_pen);

            painter.drawLine(QPointF{centre_x, centre_y}
                            , QPointF{line_side_x, centre_y - _PIXELS_BETWEEN_LINES}
                            );
            painter.drawLine(QPointF{centre_x, centre_y}
                            , QPointF{line_side_x, centre_y + _PIXELS_BETWEEN_LINES}
                            );
        }

        int top_int_value = ceiled_value + ceiled_ceiled_value;
        Float top_y = central_line_y;
        while (top_side_y <= top_y)
        {
            auto old_tiv = top_int_value;

            top_y -= this->drawLine(painter, black_pen, top_y, top_int_value);

            if (_value_rising_direction == CockpitVBarUtils::ValueRisingDirection::Up)
            {
                ++top_int_value;
            } else {
                --top_int_value;
            }

            top_int_value = this->restrictionPolicy()(top_int_value);

            if (old_tiv == top_int_value)
            {
                break;
            }
        }

        int bottom_int_value = ceiled_value + ceiled_ceiled_value;
        Float bottom_y = central_line_y;
        while (bottom_y <= bottom_side_y)
        {
            auto old_biv = bottom_int_value;

            bottom_y += this->drawLine(painter, black_pen, bottom_y, bottom_int_value);

            if (_value_rising_direction == CockpitVBarUtils::ValueRisingDirection::Down)
            {
                ++bottom_int_value;
            } else {
                --bottom_int_value;
            }

            bottom_int_value = this->restrictionPolicy()(bottom_int_value);

            if (old_biv == bottom_int_value)
            {
                break;
            }
        }
    }

private:
    static constexpr uint _PIXELS_BETWEEN_LINES = 10;
    static constexpr Float _SIDE_PADDING_MULTIPLIER = 1.0 / 20;
    static constexpr Float _LINE_LENGTH_PADDING_MULTIPLIER = 1.0 / 10;
    static constexpr Float _LONG_LINE_LENGTH_PADDING_MULTIPLIER = 1.0 / 20;

    static constexpr uint _TEXT_PEN_WIDTH = 2;
    static constexpr uint _LINE_WIDTH = 2;
    static constexpr uint _BOLD_LINE_WIDTH = 3;
    static constexpr uint _LONG_LINE_WIDTH = 3;

    static constexpr uint _TEXT_SIZE_UNITS = 4;
    static const QString _TEXT_FONT_FAMILY;

    CockpitVBarUtils::ValueRisingDirection _value_rising_direction;
    CockpitVBarUtils::TextPosition _text_position;

    void drawNumber(QPainter & painter, QPen & default_pen, Float y, int number)
    {
        const Float text_x_shift = (this->_text_position == CockpitVBarUtils::TextPosition::Left ? 0 : this->width() / 2.0);
        const Float text_area_width = this->width() / 2.0;
        const Float text_area_height = this->height();

        default_pen.setWidth(_TEXT_PEN_WIDTH);
        painter.setPen(default_pen);
        painter.setFont(CockpitBarUtils::fitTextToRect(QString::number(number)
                                                        , QFont(_TEXT_FONT_FAMILY)
                                                        , {0, 0, int(text_area_width), _PIXELS_BETWEEN_LINES * _TEXT_SIZE_UNITS}
                                                        )
                        );
        painter.drawText(
            QRectF{text_x_shift, y - text_area_height / 2, text_area_width, text_area_height}
            , Qt::AlignCenter
            , QString::number(number)
            );
    }

    uint drawLine(QPainter & painter, QPen & default_pen, Float y, int int_value)
    {
        const Float line_x_shift = (this->_text_position == CockpitVBarUtils::TextPosition::Right ? 0 : this->width() / 2.0);

        const Float line_left_x = line_x_shift + this->width() * _LINE_LENGTH_PADDING_MULTIPLIER;
        const Float line_right_x = this->width() / 2.0 + line_x_shift - this->width() * _LINE_LENGTH_PADDING_MULTIPLIER;

        const Float long_line_left_x = line_x_shift + this->width() * _LONG_LINE_LENGTH_PADDING_MULTIPLIER;
        const Float long_line_right_x = this->width() / 2.0 + line_x_shift - this->width() * _LONG_LINE_LENGTH_PADDING_MULTIPLIER;

        if (std::abs(int_value % units_between_lines.bold_long_line(int_value)) == 0)
        {
            drawNumber(painter, default_pen, y, int_value);
            default_pen.setWidth(_LONG_LINE_WIDTH);
            painter.setPen(default_pen);
            painter.drawLine(long_line_left_x, y, long_line_right_x, y);
            
            return _PIXELS_BETWEEN_LINES;
        } else if (std::abs(int_value % units_between_lines.bold_short_line(int_value)) == 0)
        {
            default_pen.setWidth(_BOLD_LINE_WIDTH);
            painter.setPen(default_pen);
            painter.drawLine(line_left_x, y, line_right_x, y);

            return _PIXELS_BETWEEN_LINES;
        } else if (std::abs(int_value % units_between_lines.short_line(int_value)) == 0) {
            default_pen.setWidth(_LINE_WIDTH);
            painter.setPen(default_pen);
            painter.drawLine(line_left_x, y, line_right_x, y);

            return _PIXELS_BETWEEN_LINES;
        }

        return 0;
    }
};

template <typename Float>
const QString CockpitVBar<Float>::_TEXT_FONT_FAMILY = "monospace";

#endif // COCKPITVBAR_H
